#include <sys/select.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include "event_loop.h"


struct Task* tasks_list_head;
int max_fd_value;  // parameter for select

struct WantedEvent
  {
	int sd;
	char event;
	char ready;
  };


struct Task
  {
	struct Task* next_task;
	struct WantedEvent * wanted_events;
	int max_events;
	int zombie;
	int ( *handler )( struct Task* );
	void *task_data;
  };


#define NULL_SD 0
#define INIT_SD -2

#define MAX(a,b) ((a)>(b))?(a):(b)
#define ADD_TASK(tp) {tp->next_task= tasks_list_head;tasks_list_head = tp;}

struct Task* create_task( int ( *handler )( struct Task* ), int max_events )
  {
	struct Task* tp;
	tp = malloc(sizeof(struct Task));
	tp->max_events = max_events;
	tp->wanted_events = calloc(max_events,sizeof(struct WantedEvent));
	tp->wanted_events[0].sd = INIT_SD;
	tp->wanted_events[0].event = EL_NOP;
	tp->handler = handler;
	tp->zombie = 0;
	tp->task_data = NULL;
	ADD_TASK(tp);
	return tp;
  }

#define REGISTER_REQUEST(t,i,d,op) {if(i<t->max_events){t->wanted_events[i].sd=d;t->wanted_events[i].event=op;t->wanted_events[i].ready=0;max_fd_value=MAX(max_fd_value,d);}}
void register_request( struct Task* tp, int index, int sd, int operation )
  {
	if( index<tp->max_events )
	  {
		tp->wanted_events[index].sd = sd;
		tp->wanted_events[index].event = operation;
		tp->wanted_events[index].ready = 0;
		max_fd_value = MAX( max_fd_value, sd );
	  }
  }
#define DEREGISTER(t,i) {t->wanted_events[i].event=EL_NOP;}
void deregister_request( struct Task* tp, int index )
  {
	tp->wanted_events[index].event = EL_NOP;
  }
  
  
#define REQUEST_IS_READY(t,i) t->wanted_events[i].ready
int request_is_ready( struct Task* tp, int index )
  {
	return tp->wanted_events[index].ready;
  }

#define DELETE_TASK(t) t->zombie=1;
void delete_task( struct Task* tp )
  {
	tp->zombie = 1;
  }

#define IS_ZOMBIE(t) (t->zombie==1)
int is_zombie( struct Task* tp )
  {
	return tp->zombie;
  }

#define TASK_DATA(t) t->task_data
void* get_task_data( struct Task* tp )
  {
	return tp->task_data;
  }

void set_task_data( struct Task* tp, void* data_pointer )
  {
	tp->task_data = data_pointer;
  }




//#define EVENT_LOOP_DEBUG

void event_loop()
  {
	int ret;
	fd_set rfd;
	fd_set wfd;
	int i;
	
	for ( ;; )
	  {
		struct Task* tp = tasks_list_head;
		struct Task* ttp;
		int something_ready;
		struct timeval * timeout_pointer;
		struct timeval timeout;
		
		FD_ZERO( &rfd );
		FD_ZERO( &wfd );
		
		//bzero( &timeout, sizeof( struct timeout ) );
		timeout.tv_sec = 0;
		timeout.tv_usec = 0;
		timeout_pointer = NULL;
		
		#ifdef EVENT_LOOP_DEBUG
		printf( "waiting_set:\n" );
		#endif
		while ( tp != NULL )
		  {
			if ( tp->wanted_events[0].sd == INIT_SD ) // qualcuno deve essere inizializzato
				{timeout_pointer = &timeout;
				#ifdef EVENT_LOOP_DEBUG
				printf( "I %p,",tp ); 
				#endif
				}
			else
				for ( i=0; i<tp->max_events; i++ )
					if ( tp->wanted_events[i].event == EL_READ )
					  { 
						FD_SET( tp->wanted_events[i].sd, &rfd );
						tp->wanted_events[i].ready = 0;
						#ifdef EVENT_LOOP_DEBUG
						printf( "R %d,",tp->wanted_events[i].sd ); 
						#endif
					  }
					else if ( tp->wanted_events[i].event == EL_WRITE )
					  {
						FD_SET( tp->wanted_events[i].sd, &wfd );
						tp->wanted_events[i].ready = 0;
						#ifdef EVENT_LOOP_DEBUG
						printf( "W %d,",tp->wanted_events[i].sd );
						#endif
					  }
			tp = tp->next_task;
		  }
		#ifdef EVENT_LOOP_DEBUG
		printf("\n");
		#endif
		  
		ret = select( max_fd_value+1, &rfd, &wfd, NULL, timeout_pointer );
		if ( ret < 0 )
			PRINT_ERROR( "select() failed" );
		
		// call handlers for ready tasks
		tp = tasks_list_head;
		while ( tp != NULL )
		  {
			something_ready = 0;
			if ( tp->wanted_events[0].sd == INIT_SD )  // initializer
			  {
				( *tp->handler )( tp );
				if ( tp->wanted_events[0].sd == INIT_SD )  // remove initialization if necessary
				  {
					tp->wanted_events[0].sd = NULL_SD;
					tp->wanted_events[0].event = EL_NOP;
				  }
			  }
			else
			  {
				for ( i=0; i<tp->max_events; i++ )
					if ( ( tp->wanted_events[i].event == EL_READ && FD_ISSET( tp->wanted_events[i].sd, &rfd ) ) || ( tp->wanted_events[i].event == EL_WRITE && FD_ISSET( tp->wanted_events[i].sd, &wfd ) ) )
					  {
						something_ready = 1;
						tp->wanted_events[i].ready = 1;
					  }
				if ( something_ready )
					( *tp->handler )( tp );
			  }
			//printf( "%d ",tp->registered_request );
			tp = tp->next_task;
		  }
		#ifdef EVENT_LOOP_DEBUG
		printf( "\n" );
		#endif
		
		// remove zombie tasks
		tp = tasks_list_head;
		ttp = NULL;
		while ( tp != NULL )
		  {
			if ( IS_ZOMBIE(tp) )
			  {
				if ( ttp == NULL ) // if ( tp == tasks_lists_head )
				  {
					tasks_list_head = tp->next_task;
					#ifdef EVENT_LOOP_DEBUG
					printf( "removing %p...\n", tp );
					#endif
					free( tp );
					tp = tasks_list_head;
				  }
				else
				  {
					ttp->next_task = tp->next_task;
					#ifdef EVENT_LOOP_DEBUG
					printf( "removing %p...\n", tp );
					#endif
					free( tp );
					tp = ttp->next_task;
				  }
			  }
			else
			  {
				ttp = tp;
				tp = tp->next_task;
			  }
		  }
	  }
  }
